import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/common/prisma/PrismaService';
import MenuVo from '../vo/MenuVo';
import { MenuDto } from '../dto/MenuDto';
import TreeVo from 'src/common/TreeVo';
import TreeUtil from 'src/common/TreeUtil';
import MenuQueryParam from '../param/MenuQueryParam';
import PageResult from 'src/common/PageResult';
import { plainToInstance } from 'class-transformer';
import ServiceException from 'src/common/ServiceException';

/*
 * @Author: jingfuu@163.com
 * @Date: 2024-11-12 14:24:06
 * @LastEditTime: 2024-11-27 13:19:54
 * @Description: 菜单service
 */
@Injectable()
class MenuService {
  constructor(private prisma: PrismaService) {}

  /**
   * 获取用户权限
   * @param userId 用户id
   * @returns 权限列表
   */
  async getUserPermissions(roleIds: number[]): Promise<string[]> {
    //先查出用户所有菜单，进行过滤出权限信息返回
    const permissions: string[] = [];
    if (roleIds.includes(1)) {
      permissions.push('*:*:*');
    }
    const roleMenus = await this.prisma.sysRoleMenu.findMany({
      where: {
        roleId: {
          in: roleIds,
        },
      },
    });
    if (roleMenus && roleMenus.length > 0) {
      const menuIds = roleMenus.map((item) => item.menuId);
      const menus = await this.prisma.sysMenu.findMany({
        where: {
          AND: [
            {
              menuId: {
                in: menuIds,
              },
            },
            {
              menuType: {
                in: ['C', 'F'],
              },
            },
            {
              status: '0',
            },
          ],
        },
        select: { perms: true },
      });
      return permissions.concat(menus.map((item) => item.perms));
    }
    return permissions;
  }

  async getUserRouters(roleIds: number[]): Promise<MenuVo[]> {
    let menuVos: MenuVo[] = [];
    const roleMenus = await this.prisma.sysRoleMenu.findMany({
      where: {
        roleId: {
          in: roleIds,
        },
      },
    });
    if ((roleMenus && roleMenus.length > 0) || roleIds.includes(1)) {
      const menuIds = roleMenus.map((item) => item.menuId);
      const whereConditions: any[] = [
        {
          menuType: {
            in: ['M', 'C'],
          },
        },
        {
          status: '0',
        },
        {
          visible: '0',
        },
      ];
      if (!roleIds.includes(1)) {
        whereConditions.push({
          menuId: {
            in: menuIds,
          },
        });
      }
      const sysMenus = await this.prisma.sysMenu.findMany({
        where: {
          AND: whereConditions,
        },
      });
      const menus: MenuDto[] = plainToInstance(MenuDto, sysMenus);
      if (menus && menus.length > 0) {
        menuVos = menus.map((item) => {
          return this.menu2Vo(item);
        });
      }
    }
    return this.buildTree(menuVos);
  }

  /**
   * 菜单转vo
   * @param menu 菜单
   * @returns 菜单vo
   */
  private menu2Vo(menu: MenuDto): MenuVo {
    const menuVo = new MenuVo();
    menuVo.component = menu.parentId == 0 ? 'Layout' : menu.component;
    menuVo.hidden = menu.visible === '1';
    menuVo.meta = {
      title: menu.menuName,
      icon: menu.icon,
      noCache: menu.isCache === 1,
      link: menu.path.startsWith('http') ? menu.path : '',
    };
    menuVo.name = menu.path.replace(/^./, (match) => match.toUpperCase());
    menuVo.sort = menu.orderNum;
    menuVo.path = menu.path;
    if (menu.parentId == 0 && !menu.path.startsWith('http')) {
      menuVo.redirect = 'noRedirect';
      menuVo.path = menu.path.startsWith('http') ? menu.path : '/' + menu.path;
    }
    menuVo.id = menu.menuId;
    menuVo.pid = menu.parentId;
    if (menu.path.startsWith('http') && menu.isFrame == 0) {
      menuVo.component = 'InnerLink';
    }
    return menuVo;
  }

  private buildTree(menuList: MenuVo[]): MenuVo[] {
    // 创建一个映射，以 pid 为键，菜单项为值
    const menuMap = new Map<number, MenuVo>();
    menuList.forEach((menu) => menuMap.set(menu.id, menu));

    // 创建一个数组来存储根菜单项
    const tree: MenuVo[] = [];

    // 遍历菜单列表，构建树形结构
    menuList.forEach((menu) => {
      if (menu.pid === 0) {
        // 如果 pid 为 0，则为根菜单项，添加到树中
        tree.push(menu);
      } else {
        // 如果 pid 不为 0，则找到父菜单项，并将当前菜单项添加到其 children 数组中
        const parent = menuMap.get(menu.pid);
        if (parent && !parent.children) {
          parent.children = [];
        }
        if (parent) {
          parent.alwaysShow = true;
          if (!parent.component) {
            parent.component = 'ParentView';
          }
          if (parent.component === 'InnerLink') {
            parent.path = '/';
            parent.component = 'Layout';
          }
          parent.children?.push(menu);
        }
      }
    });

    // 使用递归函数对每个子树按 sort 字段排序
    function sortTree(menus: MenuVo[]): void {
      menus.forEach((menu) => {
        if (menu.children) {
          menu.children.sort((a, b) => a.sort - b.sort); // 升序排序
          sortTree(menu.children); // 递归排序子树
        }
      });
    }
    // 对树进行排序
    sortTree(tree);
    return tree;
  }

  async getAllMenuTree(): Promise<TreeVo[]> {
    const menus = await this.prisma.sysMenu.findMany();
    const treeVos: TreeVo[] = menus.map((menu) => {
      return {
        id: menu.menuId,
        pid: menu.parentId,
        label: menu.menuName,
        sort: menu.orderNum,
        children: [],
      };
    });
    return TreeUtil.buildTree(treeVos);
  }

  async getCheckedMenuId(roleId: number): Promise<number[]> {
    const roleMenus = await this.prisma.sysRoleMenu.findMany({
      where: {
        roleId: roleId,
      },
    });
    return roleMenus.map((item) => item.menuId);
  }

  async list(param: MenuQueryParam): Promise<PageResult<MenuDto>> {
    const skip = param.pageNum != 0 ? (param.pageNum - 1) * param.pageSize : 0;
    const whereClause: any = this.generateMenuQueryCondition(param);
    const [total, menus] = await this.prisma.$transaction([
      this.prisma.sysMenu.count({ where: whereClause }),
      this.prisma.sysMenu.findMany({
        skip,
        take: param.pageSize,
        orderBy: {
          orderNum: 'asc',
        },
        where: whereClause,
      }),
    ]);
    const rows: MenuDto[] = menus.map((role) => plainToInstance(MenuDto, role));
    return {
      total,
      rows,
    };
  }
  generateMenuQueryCondition(param: MenuQueryParam): any {
    const whereClause: any = {};
    if (param.menuName) {
      whereClause.menuName = {
        contains: param.menuName,
      };
    }
    if (param.status !== undefined) {
      whereClause.status = param.status;
    }
    return whereClause;
  }

  async deleteMenu(id: number) {
    await this.prisma.sysMenu.delete({ where: { menuId: id } });
  }

  async getDetailById(id: number): Promise<MenuDto> {
    const sysMenu = await this.prisma.sysMenu.findUnique({
      where: { menuId: id },
    });
    const menuDto = plainToInstance(MenuDto, sysMenu);
    menuDto.isCache = menuDto.isCache + '';
    menuDto.isFrame = menuDto.isFrame + '';
    return menuDto;
  }

  async createMenu(menu: MenuDto) {
    await this.checkMenuName(menu.menuName, null, menu.parentId);
    await this.prisma.sysMenu.create({
      data: {
        parentId: menu.parentId,
        menuType: menu.menuType,
        icon: menu.icon,
        menuName: menu.menuName,
        orderNum: menu.orderNum,
        path: menu.path,
        component: menu.component,
        query: menu.query,
        isFrame: parseInt(menu.isFrame as string),
        isCache: parseInt(menu.isCache as string),
        visible: menu.visible,
        status: menu.status,
        perms: menu.perms,
        remark: menu.remark,
      },
    });
  }

  async updateMenu(menu: MenuDto) {
    await this.checkMenuName(menu.menuName, menu.menuId, menu.parentId);
    await this.prisma.sysMenu.update({
      data: {
        parentId: menu.parentId,
        menuType: menu.menuType,
        icon: menu.icon,
        menuName: menu.menuName,
        orderNum: menu.orderNum,
        path: menu.path,
        component: menu.component,
        query: menu.query,
        isFrame: parseInt(menu.isFrame as string),
        isCache: parseInt(menu.isCache as string),
        visible: menu.visible,
        status: menu.status,
        perms: menu.perms,
        remark: menu.remark,
      },
      where: {
        menuId: menu.menuId,
      },
    });
  }

  async checkMenuName(menuName: string, menuId: number, parentId: number) {
    const sysMenu = await this.prisma.sysMenu.findFirst({
      where: { menuName: menuName, parentId: parentId },
    });
    if (!menuId) {
      if (sysMenu) {
        throw new ServiceException('菜单：' + menuName + '已存在');
      }
    } else {
      if (sysMenu && sysMenu.menuId != menuId) {
        throw new ServiceException('菜单：' + menuName + '已存在');
      }
    }
  }
}

export default MenuService;
